Record 타입의 에러코드 관리하기

현재는 타입별로 익셉션이 찢어져 있으나 추후 아래의 CategoryException과 같이 하나의 거대한 에러테이블 속에 에러메시지, 상태코드, 에러코드, 그룹까지 가진 에러테이블을 만들면 별도로 XXException 클래스를 만들지 않아도 BaseException의 수혜를 볼 수 있도록 만들어야겠다.

AS-IS

throw new CategoryException('CATEGORY_NOT_FOUND');

TO-BE

에러코드만 치면 알아서 익셉션 타입과 그룹을 찾아들어감.

throw new RacketimeException('TICKET_USAGE_LIMIT_REACHED');

e.g. CategoryException

import { HttpStatus } from '@nestjs/common';

import {
  BaseException,
  UNKNOWN_ERROR,
} from '@/shared/exceptions/base.exception';

type ECode =
  | 'CATEGORY_IN_USED'
  | 'CATEGORY_NOT_FOUND'
  | 'CATEGORY_NAME_ALREADY_EXISTS';
type EContext = {
  errorMessage: string;
  statusCode: number;
  errorCode: string;
};
const errta: Record<ECode, EContext> = {
  CATEGORY_IN_USED: {
    errorMessage: '이미 사용중인 카테고리입니다.',
    statusCode: HttpStatus.FORBIDDEN,
    errorCode: 'ECATEGORY0000',
  },
  CATEGORY_NOT_FOUND: {
    errorMessage: '카테고리가 존재하지 않습니다.',
    statusCode: HttpStatus.NOT_FOUND,
    errorCode: 'ECATEGORY0001',
  },
  CATEGORY_NAME_ALREADY_EXISTS: {
    errorMessage: '카테고리 이름이 중복됩니다.',
    statusCode: HttpStatus.BAD_REQUEST,
    errorCode: 'ECATEGORY0002',
  },
};

export class CategoryException extends BaseException {
  constructor(error: ECode, payload?: any) {
    const ectx = errta[error];
    if (!ectx) {
      super('Unknown Error', HttpStatus.BAD_REQUEST, UNKNOWN_ERROR, {
        group: 'ACADEMY_CATEGORY',
        payload,
      });
    }

    super(ectx.errorMessage, ectx.statusCode, ectx.errorCode, {
      group: 'ACADEMY_CATEGORY',
      payload,
    });
  }
}

BaseException

import { HttpException } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';

export interface IBaseExcetpion {
  errorCode: string;
  statusCode: number;
  timestamp: string;
  path: string;
}

export const UNKNOWN_ERROR = '99999';

export class BaseException extends HttpException implements IBaseExcetpion {
  constructor(
    errorMessage: string,
    statusCode: number,
    errorCode: string,
    { group, payload }: { group?: string; payload?: any } = {},
  ) {
    super(errorMessage, statusCode);
    this.errorCode = errorCode;
    this.timestamp = new Date().toISOString();
    this.group = group ?? 'ERROR';
    this.payload = payload;
  }

  @ApiProperty()
  group?: string;

  @ApiProperty()
  errorCode: string;

  @ApiProperty()
  statusCode: number;

  @ApiProperty()
  timestamp: string;

  @ApiProperty()
  path: string;

  @ApiProperty()
  payload: any;
}